
package com.joysuch.wwyt.datasync.support;

import com.joysuch.wwyt.bp.entity.BpEmergencyContingencyPlans;
import com.joysuch.wwyt.datasync.DataSyncType;
import com.joysuch.wwyt.datasync.annotation.DataSync;
import com.joysuch.wwyt.datasync.entity.DataSyncInfo;
import com.joysuch.wwyt.datasync.repository.DataSyncInfoDao;
import com.joysuch.wwyt.datasync.service.DataSyncCondition;
import com.joysuch.wwyt.datasync.service.DataSyncProcessor;
import com.joysuch.wwyt.datasync.service.DataSyncRegister;
import lombok.Data;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.beans.PropertyDescriptor;
import java.util.List;

@Component
public class AopDataSyncDaoInterceptor implements MethodInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(AopDataSyncDaoInterceptor.class);

    @Resource
    @Lazy
    private DataSyncInfoDao dataSyncInfoDao;

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private DataSyncCondition condition;


    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Info info = condition.extractSyncInfo(invocation);

        if(info == null || DataSyncInfo.class.equals(info.domainClass)) {
            return invocation.proceed();
        }

        boolean shouldSync = condition.shouldSync(info);
        if (shouldSync && info.annotation != null) {
            // 执行方法前调用
            processBeforeInvoking(info);
        }
        Object object = invocation.proceed();
        if (shouldSync) {
            // 执行方法后调用
            process(info);
        }

        return object;
    }

    private void processBeforeInvoking(Info info) {
        DataSyncProcessor processor = applicationContext.getBean(info.annotation.processor());
        info.processor = processor;
        processor.processBeforeInvoking(info.domainClass, info.annotation, info.invocation.getMethod(), info.type,
                info.invocation.getArguments());
    }

    private void process(Info info) {
        if (info.annotation != null) {
            processByAnnotationAfterInvoking(info);
            return;
        }
        if (info.type == DataSyncType.ADD) {
            processAdd(info);
        } else if (info.type == DataSyncType.UPDATE) {
            processUpdate(info);
        } else if (info.type == DataSyncType.DELETE) {
            processDelete(info);
        }
    }

    private void processDelete(Info info) {
        String name = info.invocation.getMethod().getName();
        Object[] arguments = info.invocation.getArguments();
        if ("deleteInBatch".equals(name) || "delete".equals(name) || "deleteAll".equals(name)) {
            if (arguments == null || arguments.length == 0) {
                return;
            }
            for (Object arg : arguments) {
                if (arg instanceof Iterable) {
                    Iterable it = (Iterable) arg;
                    for (Object obj : it) {
                        checkAndCreateDataChangeInfo(info, obj);
                    }
                } else {
                    checkAndCreateDataChangeInfo(info, arg);
                }
            }
        } else if ("deleteById".equals(name) && arguments.length == 1 && arguments[0] != null) {
            if (info.entityInformation.getIdType().isAssignableFrom(arguments[0].getClass())) {
                // 创建数据记录
                createDataChangeInfo(info, info.invocation, arguments[0]);
            }
        } else {
            // 其他deleteBy语句
            logger.warn("暂不支持: {}", info.invocation.getMethod());
        }
    }

    private void processUpdate(Info info) {
        // 更新的处理方法目前和info一致
        processAdd(info);
    }

    private void processAdd(Info info) {
        Object[] arguments = info.invocation.getArguments();
        if (arguments == null || arguments.length == 0) {
            return;
        }
        for (Object arg : arguments) {
            if (arg instanceof Iterable) {
                Iterable it = (Iterable) arg;
                for (Object obj : it) {
                    checkAndCreateDataChangeInfo(info, obj);
                }
            } else {
                checkAndCreateDataChangeInfo(info, arg);
            }
        }
    }

    private void checkAndCreateDataChangeInfo(Info info, Object arg) {
        if (arg != null && info.domainClass.isAssignableFrom(arg.getClass())) {
            Object id = info.entityInformation.getId(arg);
            if (id != null) {
                createDataChangeInfo(info, arg, id);
            }
        }
    }

    private void createDataChangeInfo(Info info, Object arg, Object id) {
        DataSyncInfo data = new DataSyncInfo();
        data.setChangeType(info.type.getCode());
        // 暂不保存数据，防止循环引用，导致栈溢出
        // if (arg != null) {
        // data.setContent(new Gson().toJson(arg));
        // }
        data.setDataId((Long) id);
        data.setState((byte) 0);
        String serviceName = condition.getServiceName(info, arg, id);
        if (serviceName != null) {
            // 有合适的serviceName
            data.setTableName(serviceName);
            data.setTenentId((Long) reflectGetValue(arg, info.domainClass, "tenentId"));
            data.setOrgCode((String) reflectGetValue(arg, info.domainClass, "orgCode"));
            getDataSyncInfoDao().save(data);

            //文件上传处理： 有多个附件则写入多条记录
            createFileDataSyncInfo(data, info, arg);
        }
    }

    Object reflectGetValue(Object obj, Class domainClass, String prop) {
        PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(domainClass, prop);
        if (pd != null) {
            try {
                return pd.getReadMethod().invoke(obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private void createFileDataSyncInfo(DataSyncInfo data, Info info,Object arg){

        String primaryKey = null;

        // 应急预案附件
        if (info.domainClass == BpEmergencyContingencyPlans.class) {
            // 文件对象获取数据主键值
           primaryKey = ((BpEmergencyContingencyPlans) arg).getCode();
            String attachmentIds = ((BpEmergencyContingencyPlans) arg).getAttachmentId();
            if(StringUtils.isNotEmpty(attachmentIds)){
                String[] fileIds = attachmentIds.split(",");
                if(fileIds != null){
                   for(int i=0;i<fileIds.length; i++){
                       DataSyncInfo dataInfo = new DataSyncInfo();
                       BeanUtils.copyProperties(data,dataInfo);
                       dataInfo.setTableName(data.getTableName()+"_file");
                       dataInfo.setPrimaryKey(primaryKey);
                       dataInfo.setDataId(Long.valueOf(fileIds[i]));
                       dataInfo.setId(null);
                       getDataSyncInfoDao().save(dataInfo);
                   }
                }
            }
        }
    }

    private DataSyncInfoDao getDataSyncInfoDao() {
        return dataSyncInfoDao;
    }

    private void processByAnnotationAfterInvoking(Info info) {
        // 如果处理器返回了要处理的对象，那么继续处理此对象
        List entities = info.processor.processAfterInvoking(info.domainClass, info.annotation,
                info.invocation.getMethod(), info.type,
                info.invocation.getArguments());
        if (entities != null && entities.size() > 0) {
            for (Object obj : entities) {
                logger.debug("processByAnnotationAfterInvoking: " + obj);
                createDataChangeInfo(info, obj, info.entityInformation.getId(obj));
            }
        }
    }

    @Data
    public static class Info {

        public Info(MethodInvocation invocation, JpaMetamodelEntityInformation information, DataSyncType type) {
            super();
            this.entityInformation = information;
            this.type = type;
            this.domainClass = information.getJavaType();
            this.invocation = invocation;
        }

        public Info(MethodInvocation invocation, DataSync sync, JpaMetamodelEntityInformation information) {
            super();
            this.entityInformation = information;
            this.annotation = sync;
            this.domainClass = information.getJavaType();
            this.invocation = invocation;
            this.type = this.annotation.type();
        }

        DataSync annotation;
        String serviceName;
        MethodInvocation invocation;
        Class domainClass;
        JpaMetamodelEntityInformation entityInformation;
        DataSyncType type;
        // 用于存储本次实例化的处理器
        DataSyncProcessor processor;
    }
}
